#include "lua_thread.h"
#include "lua_utils.h"
#include "Win32Utils/CPathUtils.h"
#include "Win32Utils/CStrUtils.h"
#include <string>
#include <thread>
#include <future>

std::mutex lua_thread::m_mutex;
std::mutex lua_thread::m_create_mutex;
std::vector<std::thread> g_tasks;
int g_task_index = 0;

static luaL_Reg lib_functions[] =
{
    { "id", &lua_thread::id},
    { "sleep", &lua_thread::sleep},
    { "create", &lua_thread::create},
    { "create_ex", &lua_thread::create_ex},
    { "clear", &lua_thread::clear},
    { "lock", &lua_thread::lock},
    { "unlock", &lua_thread::unlock},
    { NULL, NULL}
};

void lua_thread::initialize(lua_State* L, const char* lib_name)
{
    lua_newtable(L);

    luaL_Reg* fun_info = lib_functions;
    while (fun_info->func)
    {
        lua_pushcfunction(L, fun_info->func);
        lua_setfield(L, -2, fun_info->name);
        fun_info++;
    }
    lua_setfield(L, -2, lib_name);
}

void lua_thread::uninitialize()
{
    for (auto& item : g_tasks)
    {
        if (item.joinable())
        {
            item.join();
        }
    }

    g_tasks.clear();
}

int lua_thread::lock(lua_State* L)
{
    m_mutex.lock();
    return 0;
}

int lua_thread::unlock(lua_State* L)
{
    m_mutex.unlock();
    return 0;
}

int lua_thread::id(lua_State* L)
{
    lua_pushinteger(L, GetCurrentThreadId());
    return 1;
}

int lua_thread::sleep(lua_State* L)
{
    m_mutex.lock();
    int nTop = lua_gettop(L);
    if (nTop >= 1 && lua_isnumber(L, -1))
    {
        ::Sleep(lua_tointeger(L, -1));
    }
    m_mutex.unlock();

    return 0;
}

int lua_thread::clear(lua_State* L)
{
    lua_thread::uninitialize();
    return 0;
}

int lua_thread::create(lua_State* L)
{
    m_create_mutex.lock();
    int nTop = lua_gettop(L);
    std::string task_name;

    if (nTop >= 1 && lua_isfunction(L, -1))
    {
        int ref_fun = luaL_ref(L, LUA_REGISTRYINDEX);

        std::promise<bool> p;
        std::future<bool> f = p.get_future();
        lua_State* task = lua_newthread(L);
        int ref_state = luaL_ref(L, LUA_REGISTRYINDEX);

        g_tasks.emplace_back(std::thread([&L, &task, &p, task_name, ref_state, ref_fun]() {
            
                lua_rawgeti(task, LUA_REGISTRYINDEX, ref_fun);
                p.set_value(true);

                lua_pcall(task, 0, 0, 0);

                luaL_unref(L, LUA_REGISTRYINDEX, ref_fun);
                luaL_unref(L, LUA_REGISTRYINDEX, ref_state);

                return;
            }
        ));

        std::thread::id id = g_tasks[g_tasks.size() - 1].get_id();
        _Thrd_t t = *(_Thrd_t*)&id ;
        f.get();

        lua_pushinteger(L, t._Id);
    }
    else
    {
        lua_pushinteger(L, 0);
    }

    m_create_mutex.unlock();

    return 1;
}

int lua_thread::create_ex(lua_State* L)
{
    m_create_mutex.lock();
    int nTop = lua_gettop(L);
    std::string task_name;

    if (nTop >= 1 && lua_isstring(L, -1))
    {
        task_name = lua_tostring(L, -1);

        g_tasks.emplace_back(std::thread([&L, task_name]() {

            lua_State* L = luaL_newstate();

            lua_gc(L, LUA_GCSTOP, 0);
            luaL_openlibs(L);
            lua_gc(L, LUA_GCRESTART, 0);

            int iRet = luaL_loadfile(L, task_name.c_str());
            if (0 != iRet)
            {
                return -1;
            }

            int iRes = lua_pcall(L, 0, 0, 0);

            lua_close(L);
            }
        ));

        std::thread::id id = g_tasks[g_tasks.size() - 1].get_id();
        _Thrd_id_t* t = (_Thrd_id_t*)&id ;

        lua_pushinteger(L, *t);
    }
    else
    {
        lua_pushinteger(L, 0);
    }

    return 1;
}
